New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add LiveReload functionality to Jekyll. #5142
Conversation
This PR doesn't include any significant documentation on the feature, but I figured I'd wait until some of the details get hammered out before writing anything. Other notes:
|
lib/jekyll/commands/serve.rb
Outdated
"Skips the initial site build which occurs before the server is started."], | ||
"livereload" => ["-l", "--livereload", | ||
"Use LiveReload to automatically refresh browsers"], | ||
"swf" => ["--swf", "Use Flash for WebSockets support"], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a no-go. Everybody is dropping support for flash faster than we want to turn around and have to remove it.
@envygeeks do you want revisions as separate commits to squash merge or for me to just adjust this current commit? |
@awood whatever works best for you and allows you to flow the best! We normally only ask for a rebase just before we merge it, before that, it's whatever workflow you wish to use since Github now has that fancy "view all files changed". |
I kinda like this, maybe we should add an updater like we've for normalize.css so that we can auto-update it every once in a while as it'll be quite easy to forget it exists. Other than that just a few comments and we can do another deeper review. |
So you automatically pull the file every so often? That's a really good idea. I think I will need to modify the code somewhat because it's been modified slightly to read from variables defined in the inserted script tags, but I think I can figure something else out so the stock livereload.js will work |
Okay, I've implemented the above. The PR now just uses the stock version of the most recent released |
cbfb322
to
80b4757
Compare
Looks like this is failing in JRuby. I think the issue has to do with the WebSockets server thread introduced in this PR. I'll do some investigation, but if anyone out there has some experience with the subtleties of JRuby, I'd wouldn't mind a second opinion. |
52f88a7
to
0382b3a
Compare
So I've made some changes for JRuby, but JRuby's TLS support is broken. I can either remove the TLS support for the WebSockets connection entirely or just ignore those options when running under JRuby (not sure if that's an acceptable solution though). Additionally, while the tests pass, the actual functionality under JRuby is broken. For whatever reason, the message to reload is sent but the browser doesn't react. I'll look into this issue later and try and get some tests that actually ensure receipt of the reload command. |
I have tracked the JRuby functionality issue down to a problem within either EM-Websocket or EventMachine. I've filed an issue with EM-Websocket. |
@envygeeks the Travis failures for this PR appear to be code style releated in code that I haven't modified. Otherwise I think this PR is ready for review at least. There's an outstanding issue with EM-Websocket/EventMachine under JRuby but I don't think that should block the review process. I can go ahead and address any issues the maintenance team sees while waiting to hear back from on the issue I filed for the JRuby bug. |
@awood I'll take some time this week to start reviewing /cc @jekyll/core @jekyll/ecosystem |
Looks like the JRuby issue is a known problem with an EventMachine reactor running under a thread. |
An alternative to having LiveReload just not work under JRuby would be to switch from WEBrick to an EventMachine compatible http server. Jekyll could then just start the EventMachine reactor when it's time to start serving and the reactor would manage both the LiveReload server on 35729 and the http server on 4000. I actually have a working proof-of-concept using Thin as the http server, and it's pretty nice since it removes all the need for threading from Jekyll proper and just delegates everything to the EventMachine event loop. Plus EventMachine can daemonize so LiveReload would continue to work in daemon mode (whereas it does not in this PR since that would require Jekyll spinning off two processes which I thought a little dodgy especially if one process dies and the other doesn't). The downside is that Thin doesn't run under JRuby at all. There are JRuby compatible http server (notably Puma) but they aren't EventMachine based and so there would have to be some sort of low-level shim layer that brokered between EventMachine's Connection class and the http server which would probably end up being a nightmare. But if Thin ever runs under JRuby, I think the strategy described above would be a worthwhile refactoring. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, just a few things that probably need changes. 😄
lib/jekyll/commands/serve.rb
Outdated
validate_options(opts) | ||
|
||
opts["watch"] = true unless opts.key?("watch") | ||
opts["livereload_port"] ||= LIVERELOAD_PORT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You've used Hash#key?
for "watch"
and ||=
for "livereload_port"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I did that because "watch" can legitimately be false
. I can line them up so they both use key
. I think I tried to keep the ||=
where possible because that seemed to be the preferred idiom elsewhere in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, my mistake! 😊
lib/jekyll/commands/serve.rb
Outdated
" of LiveReload." | ||
end | ||
elsif opts["livereload_min_delay"] || | ||
opts["liverealod_max_delay"] || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo!
- opts["liverealod_max_delay"] ||
+ opts["livereload_max_delay"] ||
lib/jekyll/commands/serve.rb
Outdated
opts["livereload_ignore"] || | ||
opts["livereload_port"] | ||
Jekyll.logger.warn "The --livereload-min-delay, --livereload-max-delay, "\ | ||
"--livereoload-ignore, and --livereload-port options require the "\ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo!
- "--livereoload-ignore, and --livereload-port options require the "\
+ "--livereload-ignore, and --livereload-port options require the "\
lib/jekyll/commands/serve.rb
Outdated
def start_callback(detached) | ||
unless detached | ||
proc do | ||
Jekyll.logger.info("Server running...", "press ctrl-c to stop.") | ||
@running << "." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel that Queue
is the wrong data type for this usage. Correct me if I'm wrong, but you never seem to actually wait for the Queue
to become populated and even if you did, there are probably better data types for this usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm using it as a mechanism to block the test from running until the server(s) can successfully start. The only place it's actually waiting for the queue to be populated is in test_commands_serve.rb
. The test starts a Serve
thread and Serve
starts a WEBrick thread and joins it so there's not another good way I could think of to know whether WEBrick is actually up and running.
I think Queue is the appropriate choice here; the docs say "[The Queue] class provides a way to synchronize communication between threads."
That being said, I do think that it's kind of crummy to have cruft in there that's only really used for the tests. I'm totally open to suggestions on a better way to handle this situation.
😆 I've never liked LiveReload but I'm sure it'll be a very useful feature for many Jekyll users. |
I've never really found it useful, I can F5 as fast as that thing loads lol. But at this point we need to bring in @jekyll/ecosystem and @jekyll/core so they can comment on your suggestions. |
But you're polling in the test, not using the actual |
@DirtyF Conflicts resolved |
Can you skip the specs that use LiveReload on Windows if they won't work? |
* Improve variable names * Improve resiliency when conflicting options are given * Refactor the LiveReloadReactor class into a separate file * Correct typos * Use alias_method for brevity * Use __dir__ instead of File.dirname(__FILE__) * Set and use default LiveReload port
Done! |
💯 A big thanks @awood for all the work you put into this, I hope this really improves the developer experience for a majority of Jekyll users. @jekyllbot: merge +minor |
If I add the But if I add the rake aborted!
NoMethodError: undefined method `start' for nil:NilClass
/Users/frank/code/jekyll/jekyll/lib/jekyll/commands/serve.rb:224:in `start_up_webrick'
/Users/frank/code/jekyll/jekyll/lib/jekyll/commands/serve.rb:104:in `process'
/Users/frank/code/jekyll/jekyll/rake/site.rake:37:in `block (2 levels) in <top (required)>'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/task.rb:251:in `block in execute'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/task.rb:251:in `each'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/task.rb:251:in `execute'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/task.rb:195:in `block in invoke_with_call_chain'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/2.4.0/monitor.rb:214:in `mon_synchronize'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/task.rb:188:in `invoke_with_call_chain'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/task.rb:181:in `invoke'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:160:in `invoke_task'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:116:in `block (2 levels) in top_level'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:116:in `each'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:116:in `block in top_level'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:125:in `run_with_threads'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:110:in `top_level'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:83:in `block in run'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:186:in `standard_exception_handling'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/lib/rake/application.rb:80:in `run'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/rake-12.3.0/exe/rake:27:in `<top (required)>'
/Users/frank/.rbenv/versions/2.4.2/bin/rake:23:in `load'
/Users/frank/.rbenv/versions/2.4.2/bin/rake:23:in `<top (required)>'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/cli/exec.rb:75:in `load'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/cli/exec.rb:75:in `kernel_load'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/cli/exec.rb:28:in `run'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/cli.rb:424:in `exec'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/cli.rb:27:in `dispatch'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/cli.rb:18:in `start'
/Users/frank/.rbenv/versions/2.4.2/bin/bundle:30:in `block in <main>'
/Users/frank/.rbenv/versions/2.4.2/lib/ruby/site_ruby/2.4.0/bundler/friendly_errors.rb:122:in `with_friendly_errors'
/Users/frank/.rbenv/versions/2.4.2/bin/bundle:22:in `<main>'
Tasks: TOP => site:preview |
Hi, can you confirm that livereload is not supported on Windows please ? |
@burrich |
Ok thanks @ashmaroli , i will try. |
@burrich If I recall correctly, it won't work with Ruby 2.4 on Windows due to a dependency (EventMachine) not working. |
@burrich That issue is being tracked here 👉 eventmachine/eventmachine#806 |
# EventMachine's SSL support in Windows requires building the gem's | ||
# native extensions against OpenSSL and that proved to be a process | ||
# so tedious that expecting users to do it is a non-starter. | ||
Jekyll.logger.abort_with "Error:", "LiveReload does not support SSL" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've stepped way back from this PR, never had the time to look at it as my plugin was just working for me. However, Livereload can support SSL, I have support for it in my plugin RobertDeRose/jekyll-livereload@34df507
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @RobertDeRose, I was using your plugin until now :)
Are you interested in submitting a patch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might, if I can find the time. So, that's a big maybe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@RobertDeRose The problem wasn't with LiveReload not supporting SSL; it was with getting EventMachine and OpenSSL to cooperated on Windows. I actually had it working fine in Linux, but getting the OpenSSL libraries to install and work on the Windows CI environment was a nightmare, so I just gave up 😭. I figured most people would only be using LiveReload for rapid prototyping so I didn't consider SSL support to be a must-have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@awood that's a fair point, I never did testing for Windows, and to be honest, not a lot of benefit for it when developing locally, unless you really need to ensure SSL isn't messing with thing, but at that point, you don't really need live reload. OK ignore my extremely late to the party comments then.
Fixed ? |
Invoke using
jekyll serve --livereload
. The Serve command will injectsome necessary JavaScript links into pages. When loaded in a browser,
this JavaScript opens a connection over port 35729 using WebSockets to
an EventMachine reactor listening to that port in a separate thread.
When Jekyll finishes rendering a page,
--livereload
hooks create aJSON message with the relative URL of the refreshed page. This message
is then pushed to the browser over the WebSockets connection. The
client-side JavaScript then reloads the page if the URL in the message
matches the URL of the current page.
Implements #4644